﻿using System;

namespace Notoric.Model.Utils
{
    /// <summary>
    /// Simple hash table mapping durations to user-defined elements.
    /// </summary>
    /// <remarks>
    /// The hash function is implemented via a call to <see cref="Duration.HighestBit"/> method.
    /// The computation is a O(1) operation and no collision is possible.
    /// </remarks>
    /// <typeparam name="T">Type of the hashed elements</typeparam>
    public class DurationHashTable<T>
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="DurationHashTable{T}"/> instance.
        /// </summary>
        /// <param name="durationFields">
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// The number of duration slots in the created hash table.
        /// Minimum hashed duration will be 1 128-th beat 
        /// and maximum 2^(<paramref name="durationFields"/> - 1) 128-th beats.
        /// </para>
        /// </param>
        /// <param name="dotsFields">
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// The number of dots slots in the created hash table
        /// that will be added to each duration slot.
        /// Minimum is 1 slot (= no dot).
        /// </para>
        /// </param>
        /// <exception cref="ArgumentOutOfRangeException">
        /// <paramref name="durationFields"/> or <paramref name="dotsFields"/> is equal to or less than 0.
        /// </exception>
        public DurationHashTable(int durationFields, int dotsFields)
        {
            if (durationFields <= 0)
                throw new ArgumentOutOfRangeException("durationFields");
            if (dotsFields <= 0)
                throw new ArgumentOutOfRangeException("dotsFields");

            hashTable = new T[durationFields, dotsFields];
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Applies a transformation function to each element of the hash table.
        /// </summary>
        /// <param name="transform">
        /// <para>
        /// Type: <see cref="Func{T, TRes}"/>
        /// </para>
        /// <para>
        /// The transformation function that will be applied.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="transform"/> is <see langword="null"/>.
        /// </exception>
        public void Transform(Func<T, T> transform)
        {
            if (transform == null)
                throw new ArgumentNullException("transform");

            for (int i = 0; i < hashTable.GetLength(0); ++i)
            {
                for (int j = 0; j < hashTable.GetLength(1); ++j)
                    hashTable[i, j] = transform(hashTable[i, j]);
            }
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the element corresponding to a given basic duration.
        /// </summary>
        /// <param name="duration">
        /// <para>
        /// Type: <see cref="UInt16"/>
        /// </para>
        /// <para>
        /// The basic duration (without augmentation dots) for which the element is being retrieved or set.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <typeparamref name="T"/>
        /// </para>
        /// <para>
        /// The element that corresponds to the given basic duration.
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentOutOfRangeException">
        /// The <paramref name="duration"/> is less than the minimum or greater than the maximum hashed value.
        /// </exception>
        public T this[ushort duration]
        {
            get
            {
                return hashTable[Duration.HighestBit(duration), 0];
            }
            set
            {
                hashTable[Duration.HighestBit(duration), 0] = value;
            }
        }

        /// <summary>
        /// Gets or sets the element corresponding to a given basic duration and dots count.
        /// </summary>
        /// <param name="duration">
        /// <para>
        /// Type: <see cref="UInt16"/>
        /// </para>
        /// <para>
        /// The basic duration (without augmentation dots) for which the element is being retrieved or set.
        /// </para>
        /// </param>
        /// <param name="dots">
        /// <para>
        /// Type: <see cref="Byte"/>
        /// </para>
        /// <para>
        /// The number of augmentation dots for which the element is being retrieved or set.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <typeparamref name="T"/>
        /// </para>
        /// <para>
        /// The element that corresponds to the given basic duration.
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentOutOfRangeException">
        /// The <paramref name="duration"/> is less than the minimum or greater than the maximum hashed value.<br/>
        /// -or-<br/>
        /// The number of <paramref name="dots"/> if greater than the maximum hashed value.
        /// </exception>
        public T this[ushort duration, byte dots]
        {
            get { return hashTable[Duration.HighestBit(duration), dots]; }
            set { hashTable[Duration.HighestBit(duration), dots] = value; }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the hash table.
        /// </summary>
        private T[,] hashTable;

        #endregion
    }
}
